#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "../memmap.h"
#include "../log.h"
#include "../core.h"
#include "../host/host.h"

#include "../hle/__sceGe_user.h"

#include "ge.h"
#include "gpu.h"
#include "display.h"
#include "pipeline.h"
#include "texture.h"

#include "dlint.h"

#ifndef max
# define max(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
# define min(a, b) (((a) > (b)) ? (b) : (a))
#endif

#define MAX_DL 200

u32 dlId = 1;

typedef struct
{
    u32 id;
    u32 pc;
    u32 stall;
} DisplayList;

DisplayList dlQueue[MAX_DL]; /* TODO: STATIC */
u32 numDL = 0;
#ifndef NO_THREAD
Mutex dlMtx;
#endif

#ifndef NO_THREAD
void dl_initTh()
{
    cs_init(&dlMtx, "Display List Stack");
}

void dl_freeTh()
{
    cs_free(&dlMtx);
}
#endif

u32 dl_enqueue(u32 pc, u32 stall)
{
    DisplayList dl;
    dl.id = dlId++;
    dl.pc = pc & 0xfffffff;
    dl.stall = stall & 0xfffffff;

    cs_enter(&dlMtx);
    if (numDL >= MAX_DL)
    {
        _log(ERR, GFX, "Too much running display lists: max %d!", MAX_DL);
        cs_leave(&dlMtx);
        return 0;
    }
    dlQueue[numDL++] = dl;
    cs_leave(&dlMtx);

    dl_interpret(dl.id);
    return dl.id;
}

void dl_chStall(u32 listid, u32 newstall)
{
    u32 i;
    cs_enter(&dlMtx);
    for (i = 0; i < numDL; i++)
        if (dlQueue[i].id == listid)
            dlQueue[i].stall = newstall & 0xfffffff;
    cs_leave(&dlMtx);
    
    dl_interpret(listid);
}

void dl_dequeue(u32 id)
{
    u32 i, j;

    cs_enter(&dlMtx);
    for (i = 0; i < numDL; i++)
    {
        if (dlQueue[i].id == id)
        {
            numDL--;
            for (j = i; j < numDL; j++)
                dlQueue[j] = dlQueue[j + 1];
            break;
        }
    }
    cs_leave(&dlMtx);
}

#ifndef NO_THREAD
void dl_interpret(u32 id)
{
    core_addToGfxThStack(GFXTH_DLINT, id);
}

void dl_interpretTh(u32 id)
#else
void dl_interpret(u32 id)
#endif
{
    u32 stack[2]; /* TODO: STATIC */
    u32 stackPtr = 0;
    u32 prev = 0;
    u32 op = 0;
    u32 data;
    u32 pc = 0;
    u32 stallAddr;
    u32 i;

    cs_enter(&dlMtx);
    for (i = 0; i < numDL; i++)
    {
        if (dlQueue[i].id == id)
        {
            pc = dlQueue[i].pc;
            stallAddr = dlQueue[i].stall;
            break;
        }
    }
    cs_leave(&dlMtx);

    if (pc == 0) {
        _log(ERR, GFX, "DL %d not found!", dlQueue[i].id);
        return;
    }

    _log(INF, GFX, "Starting DL execution at %08x - stall = %08x", pc, stallAddr);

    gstate.worldmtxnum = gstate.viewmtxnum = gstate.projmtxnum = gstate.texmtxnum = 0;

    for (;;)
    {
        if (pc == stallAddr) {
            _log(TRC, GFX, "Got stall - stopping DL interpreting");
            return;
        }

        prev = op;

        op = mem_read32(pc); /* read from memory */
        data = op & 0xffffff;
        _log(TRC, GFX, "pc: %08x => %02x & %06x", pc, op >> 24, data);

        switch (op >> 24)
        {
        case GE_CMD_NOP:
            break;

        case GE_CMD_VADDR: 
            gstate.vertexAddr = ((gstate.base << 24) | data);
            _log(TRC, GFX, "VADDR: %06x", gstate.vertexAddr);
            break;

        case GE_CMD_IADDR:  
            gstate.indexAddr = (gstate.base << 24) | data;
            _log(TRC, GFX, "IADDR: %06x", gstate.indexAddr);
            break;

        case GE_CMD_PRIM:
            {
                u32 count = data & 0xffff;
                u8 type = (GEPrimitiveType)((data >> 16) & 7);
                static const char *types[7] = {
                    "POINTS = 0",
                    "LINES = 1",
                    "LINE_STRIP = 2",
                    "TRIANGLES = 3",
                    "TRIANGLE_STRIP = 4",
                    "TRIANGLE_FAN = 5",
                    "RECTANGLES = 6",
                };
                _log(TRC, GFX, "PRIM: type: %s, count: %d, vaddr: %08x, iaddr: %08x", types[type], count, gstate.vertexAddr, gstate.indexAddr);
                if (type == GE_PRIM_SPRITES)
                    spritePipeline(gstate.vertexAddr, gstate.indexAddr, count);
                else
                    primPipeline(gstate.vertexAddr, gstate.indexAddr, type, count);
            }
            break;

        case GE_CMD_BEZIER:
            {
                u8 ucount = data & 0xff;
                u8 vcount = (data >> 8) & 0xff;
                _log(WRN, GFX, "BEZIER: U %d, V %d,", ucount, vcount);
                bezierPipeline(gstate.vertexAddr, gstate.indexAddr, ucount, vcount);
            }
            break;

        case GE_CMD_SPLINE:
            _log(WRN, GFX, "SPLINE: U %d V %d Utype %d Vtype %d", data & 0xff, (data >> 8) & 0xff, (data >> 16) & 3, (data >> 18) & 3);
            break;

        case GE_CMD_BBOX:
            _log(WRN, GFX, "BBOX: %d vertices to test", data & 0xffff);
            break;

        case GE_CMD_JUMP: 
            {
                u32 target = ((gstate.base << 24) | data);
                _log(TRC, GFX, "JUMP: %08x to %08x", pc, target);
                pc = target;
            }
            continue;

        case GE_CMD_BJUMP:
            {
                u32 target = (gstate.base << 24) | data;
                _log(WRN, GFX, "BJUMP: %08x to %08x", pc, target);
                /*pc = target;*/
            }
            break;

        case GE_CMD_CALL: 
            {
                u32 retval = pc + 4;
                u32 target = ((gstate.base << 24) | data);
                stack[stackPtr++] = retval;
                _log(WRN, GFX, "CALL: %08x to %08x, ret = %08x", pc, target, retval);
                pc = target;
            }
            continue;

        case GE_CMD_RET: 
            {
                u32 target = stack[--stackPtr] & 0xfffffff;
                _log(WRN, GFX, "RET: from %08x to %08x", pc, target);
                pc = target;
            }
            continue; /* do not increase pc */

        case GE_CMD_END: 
            _log(TRC, GFX, "END");
            if (prev >> 24 == GE_CMD_FINISH) {
                dl_dequeue(id);
                return;
            }
            else
                _log(TRC, GFX, "Ah, not finished: %06x", prev & 0xffffff);
            break;

        case GE_CMD_SIGNAL:
            {
#ifndef NO_THREAD
                u8 behavior = (data >> 16) & 0xff;
#endif
                _log(TRC, GFX, "SIGNAL");

#ifndef NO_THREAD
                __KernelWaitCallbackEndFromGfxTh();
#endif

                __KernelCallGeSignalCb(data & 0xffff); /* Run the callback */

#ifndef NO_THREAD
                if (behavior == GE_BEHAVIOR_SUSPEND)
                    __KernelWaitCallbackEndFromGfxTh();
#endif
            }
            break;

        case GE_CMD_FINISH:
            _log(TRC, GFX, "FINISH");
#ifndef NO_THREAD
            __KernelWaitCallbackEndFromGfxTh();
#endif
            __KernelCallGeFinishCb(data & 0xffff); /* Run the callback */
#ifndef NO_THREAD
            __KernelWaitCallbackEndFromGfxTh();
#endif
            break;

        case GE_CMD_BASE:
            gstate.base = (data & 0xf0000) >> 16;
            _log(TRC, GFX, "BASE: base %d", gstate.base);
            break;

        case GE_CMD_VTYPE:
            gstate.vertType = data;
            _log(TRC, GFX, "VTYPE: %06x", gstate.vertType);
            break;

        case GE_CMD_OFFSET:
            _log(WRN, GFX, "OFFSET: %06x", data);
            break;

        case GE_CMD_ORIGIN:
            _log(WRN, GFX, "ORIGIN: %06x", data);
            /*gstate.offsetAddr = data;*/
            break;

        case GE_CMD_REGION1:
            {
                s32 x1 = data & 0x3ff;
                s32 y1 = data >> 10;
                _log(WRN, GFX, "REGION1 start: %d %d", x1, y1);
            }
            break;

        case GE_CMD_REGION2:
            {
                s32 x2 = data & 0x3ff;
                s32 y2 = data >> 10;
                _log(WRN, GFX, "REGION2 end: %d %d", x2, y2);
            }
            break;

        case GE_CMD_LTE:
            gstate.lightingEnable = data;
            _log(TRC, GFX, "LTE: %d", gstate.lightingEnable);
            break;

        case GE_CMD_LTE0:
        case GE_CMD_LTE1:
        case GE_CMD_LTE2:
        case GE_CMD_LTE3:
            gstate.lightEnable[(op >> 24) - GE_CMD_LTE0] = data;
            _log(TRC, GFX, "LTE%d: %06x", (op >> 24) - GE_CMD_LTE0, data);
            break;

        case GE_CMD_CPE:
            /* We always clip with OpenGL ? */
            _log(WRN, GFX, "CPE: %06x", data);
            break;

        case GE_CMD_BCE:
            _log(TRC, GFX, "BCE: %d", data & 1);
            host_setCapability(RE_CAPABILITY_CULL_FACE, data & 1);
            gstate.cullFaceEnabled = data & 1;
            break;

        case GE_CMD_TME:
            gstate.textureMapEnable = data;
            _log(TRC, GFX, "TME: %d", gstate.textureMapEnable);
            host_setCapability(RE_CAPABILITY_TEXTURE_2D, gstate.textureMapEnable);
            break;

        case GE_CMD_FGE:
            _log(WRN, GFX, "FGE: %06x", data);
            /*gstate.fogEnable = 1;*/
            break;

        case GE_CMD_DTE:
            _log(TRC, GFX, "DTE: %d", data & 1);
            host_setCapability(RE_CAPABILITY_DITHER, data);
            break;

        case GE_CMD_ABE:
            _log(TRC, GFX, "ABE: %d", data & 1);
            host_setCapability(RE_CAPABILITY_BLEND, data & 1);
            break;

        case GE_CMD_ATE:
            _log(TRC, GFX, "ATE: %d", data & 1);
            host_setCapability(RE_CAPABILITY_ALPHA_TEST, data & 1);
            break;

        case GE_CMD_ZTE:
            _log(TRC, GFX, "ZTE: %d", data & 1);
            host_setCapability(RE_CAPABILITY_DEPTH_TEST, data & 1);
            break;

        case GE_CMD_STE:
            _log(WRN, GFX, "STE: %06x", data);
            break;

        case GE_CMD_AAE:
            _log(WRN, GFX, "AAE: %06x", data);
            break;

        case GE_CMD_PCE:
            _log(WRN, GFX, "PCE: %06x", data);
            break;

        case GE_CMD_CTE:
            _log(WRN, GFX, "CTE: %06x", data);
            break;

        case GE_CMD_LOE:
            _log(WRN, GFX, "LOE: %06x", data);
            break;

        case GE_CMD_BOFS:
            gstate.boneMatrixNumber = data;
            _log(TRC, GFX, "BOFS: %06x", gstate.boneMatrixNumber);
            break;

        case GE_CMD_BONE:
            _log(TRC, GFX, "BONE: %f (#%d)", getFloat24(data), gstate.boneMatrixNumber);
            boneMatrix[gstate.boneMatrixNumber++] = getFloat24(data);
            break;

        case GE_CMD_MW0:
        case GE_CMD_MW1:
        case GE_CMD_MW2:
        case GE_CMD_MW3:
        case GE_CMD_MW4:
        case GE_CMD_MW5:
        case GE_CMD_MW6:
        case GE_CMD_MW7:
            _log(TRC, GFX, "MW%d: %f", (op >> 24) - GE_CMD_MW0, getFloat24(data));
            gstate.morphWeights[(op >> 24) - GE_CMD_MW0] = getFloat24(data);
            break;

        case GE_CMD_PSUB:
            gstate.psubs = data & 0xff;
            gstate.psubt = (data >> 8) & 0xff;
            _log(WRN, GFX, "PSUB: S %d T %d", data & 0xff, (data >> 8) & 0xff);
            break;

        case GE_CMD_PPRIM:
            _log(WRN, GFX, "PPRIM: %d", data);
            break;

        case GE_CMD_PFACE:
            _log(WRN, GFX, "PFACE: %d", data);
            break;
 
        case GE_CMD_WMS:
            _log(TRC, GFX, "WMS: %d", gstate.worldmtxnum);
            gstate.worldmtxnum = data & 0xf;
            break;

        case GE_CMD_WORLD:
            _log(TRC, GFX, "WORLD: %f", getFloat24(data));
            worldMatrix[gstate.worldmtxnum++] = getFloat24(data);
            break;

        case GE_CMD_VMS:
            _log(TRC, GFX, "VMS: %d", gstate.viewmtxnum);
            gstate.viewmtxnum = data & 0xf;
            break;

        case GE_CMD_VIEW:
            _log(TRC, GFX, "VIEW: %f", getFloat24(data));
            viewMatrix[gstate.viewmtxnum++] = getFloat24(data);
            break;

        case GE_CMD_PMS:
            _log(TRC, GFX, "PMS: %d", gstate.projmtxnum);
            gstate.projmtxnum = data & 0xf;
            break;

        case GE_CMD_PROJ:
            _log(TRC, GFX, "PROJ: %f [%d]", getFloat24(data), gstate.projmtxnum);
            projMatrix[gstate.projmtxnum++] = getFloat24(data);
            gstate.projChanged = 1;
            break;

        case GE_CMD_TMS:
            gstate.texmtxnum = data & 0xf;
            _log(TRC, GFX, "TMS: %d", gstate.texmtxnum);
            break;

        case GE_CMD_TMATRIX:
            _log(TRC, GFX, "TMATRIX: %f", getFloat24(data));
            tgenMatrix[gstate.texmtxnum++] = getFloat24(data);
            break;

        case GE_CMD_XSCALE:
            gstate.xscale = (s32)getFloat24(data);
            _log(TRC, GFX, "XSCALE: %d", gstate.xscale);
            break;

        case GE_CMD_YSCALE:
            gstate.yscale = (s32)getFloat24(data);
            gstate.yscale = max(gstate.yscale, -gstate.yscale); /* TODO: reverse screen only when yscale < 0 */
            _log(TRC, GFX, "YSCALE: %d", gstate.yscale);
            break;

        case GE_CMD_ZSCALE:
            gstate.zscale = getFloat24(data) / 65535.0f;
            _log(TRC, GFX, "ZSCALE: %f", gstate.zscale);
            gstate.projChanged = 1;
            break;

        case GE_CMD_XPOS:
            gstate.xpos = (s32)getFloat24(data);
            _log(TRC, GFX, "XPOS: %d", gstate.xpos);
            break;

        case GE_CMD_YPOS:
            gstate.ypos = (s32)getFloat24(data);
            _log(TRC, GFX, "YPOS: %d", gstate.ypos);
            break;

        case GE_CMD_ZPOS:
            gstate.zpos = getFloat24(data) / 65535.0f;
            _log(TRC, GFX, "ZPOS: %f", gstate.zpos);
            gstate.projChanged = 1;
            break;

        case GE_CMD_USCALE: 
            gstate.uScale = getFloat24(data);
            _log(TRC, GFX, "USCALE: %f", gstate.uScale);
            break;

        case GE_CMD_VSCALE:
            gstate.vScale = getFloat24(data);
            _log(TRC, GFX, "VSCALE: %f", gstate.vScale);
            break;

        case GE_CMD_UOFFSET:
            gstate.uOff = getFloat24(data);  
            _log(TRC, GFX, "UOFFSET: %f", gstate.uOff);
            break;

        case GE_CMD_VOFFSET: 
            gstate.vOff = getFloat24(data);  
            _log(TRC, GFX, "VOFFSET: %f", gstate.vOff);
            break;

        case GE_CMD_OFFSETX:
            gstate.offsetx = data >> 4;
            _log(TRC, GFX, "OFFSETX: %d", gstate.offsetx);
            break;

        case GE_CMD_OFFSETY:
            gstate.offsety = data >> 4;
            _log(TRC, GFX, "OFFSETY: %d", gstate.offsety);
            break;

        case GE_CMD_SHADE:
            {
                GEShadeModel model;
                if (data & 1)
                    model = GE_SHADE_SMOOTH;
                else
                    model = GE_SHADE_FLAT;
                host_setShadeModel(model);
                _log(TRC, GFX, "SHADE: %d", data);
            }
            break;

        case GE_CMD_RNORM:
            gstate.reversenormals = data;
            _log(TRC, GFX, "RNORM: %06x", gstate.reversenormals);
            break;

        case GE_CMD_CMAT:
            gstate.materialupdate = data;
            _log(TRC, GFX, "CMAT: %d", gstate.materialupdate);
            break;

        case GE_CMD_EMC:
            gstate.materialemissive = data;
            _log(TRC, GFX, "EMC: %06x", gstate.materialemissive);
            break;

        case GE_CMD_AMC:
            gstate.materialambient[0] = ((data >> 0 ) & 0xff) / 255.0f;
            gstate.materialambient[1] = ((data >> 8 ) & 0xff) / 255.0f;
            gstate.materialambient[2] = ((data >> 16) & 0xff) / 255.0f;
            _log(TRC, GFX, "AMC: %f, %f, %f", gstate.materialambient[0], gstate.materialambient[1], gstate.materialambient[2]);
            break;

        case GE_CMD_DMC:
            gstate.materialdiffuse = data;
            _log(TRC, GFX, "DMC: %06x", gstate.materialdiffuse);
            break;

        case GE_CMD_SMC:
            gstate.materialspecular = data;
            _log(TRC, GFX, "SMC: %06x", gstate.materialspecular);
            break;

        case GE_CMD_AMA:
            gstate.materialambient[3] = (data & 0xff) / 255.0f;
            _log(TRC, GFX, "AMA: %f", gstate.materialambient[3]);
            break;

        case GE_CMD_SPOW:
            _log(TRC, GFX, "SPOW: %f", gstate.materialspecularcoef);
            gstate.materialspecularcoef = getFloat24(data);
            break;

        case GE_CMD_ALC:
            _log(TRC, GFX, "ALC: %06x", gstate.ambientcolor);
            gstate.ambientcolor = data;
            break;

        case GE_CMD_ALA:
            _log(TRC, GFX, "ALA: %06x", gstate.ambientalpha);
            gstate.ambientalpha = data;
            break;

        case GE_CMD_LMODE:
            _log(WRN, GFX, "LMODE: %06x", data);
            break;

        case GE_CMD_LT0:
        case GE_CMD_LT1:
        case GE_CMD_LT2:
        case GE_CMD_LT3:
            gstate.ltype[(op >> 24) - GE_CMD_LT0] = data;
            _log(TRC, GFX, "LT%d: %06x", (op >> 24) - GE_CMD_LT0, data);
            break;

        case GE_CMD_LXP0: case GE_CMD_LYP0: case GE_CMD_LZP0:
        case GE_CMD_LXP1: case GE_CMD_LYP1: case GE_CMD_LZP1:
        case GE_CMD_LXP2: case GE_CMD_LYP2: case GE_CMD_LZP2:
        case GE_CMD_LXP3: case GE_CMD_LYP3: case GE_CMD_LZP3:
            {
                s32 n = (op >> 24) - GE_CMD_LXP0;
                s32 l = n / 3;
                s32 c = n % 3;
                float val = getFloat24(data);
                _log(TRC, GFX, "L%cP%d: %f", c + 'X', l, val);
                gstate.lightpos[l][c] = val;
            }
            break;

        case GE_CMD_LXD0: case GE_CMD_LYD0: case GE_CMD_LZD0:
        case GE_CMD_LXD1: case GE_CMD_LYD1: case GE_CMD_LZD1:
        case GE_CMD_LXD2: case GE_CMD_LYD2: case GE_CMD_LZD2:
        case GE_CMD_LXD3: case GE_CMD_LYD3: case GE_CMD_LZD3:
            {
                s32 n = (op >> 24) - GE_CMD_LXD0;
                s32 l = n / 3;
                s32 c = n % 3;
                float val = getFloat24(data);
                _log(TRC, GFX, "L%cD%d: %f", c + 'X', l, val);
                gstate.lightdir[l][c] = val;
            }
            break;

        case GE_CMD_LCA0: case GE_CMD_LLA0: case GE_CMD_LQA0:
        case GE_CMD_LCA1: case GE_CMD_LLA1: case GE_CMD_LQA1:
        case GE_CMD_LCA2: case GE_CMD_LLA2: case GE_CMD_LQA2:
        case GE_CMD_LCA3: case GE_CMD_LLA3: case GE_CMD_LQA3:
            {
                s32 n = (op >> 24) - GE_CMD_LCA0;
                s32 l = n / 3;
                s32 c = n % 3;
                float val = getFloat24(data);
                _log(TRC, GFX, "L%cA%d: %f", (c == 0) ? 'C' : ((c == 1) ? 'L':'Q'), l, val);
                gstate.lightatt[l][c] = val;
            }
            break;

        case GE_CMD_SLE0:
        case GE_CMD_SLE1:
        case GE_CMD_SLE2:
        case GE_CMD_SLE3:
            _log(WRN, GFX, "SLE%d", (op >> 24) - GE_CMD_SLE0);
            break;

        case GE_CMD_SLCO0:
        case GE_CMD_SLCO1:
        case GE_CMD_SLCO2:
        case GE_CMD_SLCO3:
            _log(WRN, GFX, "SLCO%d", (op >> 24) - GE_CMD_SLC0);
            break;

        case GE_CMD_ALC0: case GE_CMD_ALC1: case GE_CMD_ALC2: case GE_CMD_ALC3:
        case GE_CMD_DLC0: case GE_CMD_DLC1: case GE_CMD_DLC2: case GE_CMD_DLC3:
        case GE_CMD_SLC0: case GE_CMD_SLC1: case GE_CMD_SLC2: case GE_CMD_SLC3:
            {
                float r = (float)(data >> 16) / 255.0f;
                float g = (float)((data >> 8) & 0xff) / 255.0f;
                float b = (float)(data & 0xff) / 255.0f;

                s32 l = ((op >> 24) - GE_CMD_ALC0) / 3;
                s32 t = ((op >> 24) - GE_CMD_ALC0) % 3;
                _log(TRC, GFX, "%cLC%d: %06x", (t == 0) ? 'A':((t == 1) ? 'D':'S'), l);
                gstate.lightColor[t][l].r = r;
                gstate.lightColor[t][l].g = g;
                gstate.lightColor[t][l].b = b;
            }
            break;

        case GE_CMD_FFACE:
            {
                GEFrontFaceType face = data ? GE_FRONTFACE_CW : GE_FRONTFACE_CCW;
                _log(TRC, GFX, "FFACE: %s", data ? "clockwise" : "counter-clockwise");
                host_setFrontFace(face);
            }
            break;

        case GE_CMD_FBP:
            _log(TRC, GFX, "FBP: %06x", data);
            gstate.frameBufferLow = data;
            break;

        case GE_CMD_FBW:
            gstate.frameBufferHigh = (data >> 16) & 0xff;
            gstate.frameBufferWidth = data & 0xffff;
            _log(TRC, GFX, "FBW: hi %02x, width %04x", gstate.frameBufferHigh, gstate.frameBufferWidth);
            break;

        case GE_CMD_ZBP:
            _log(WRN, GFX, "ZBP: %06x",  data);
            break;

        case GE_CMD_ZBW:
            _log(WRN, GFX, "ZBW: %06x",  data);
            break;

        case GE_CMD_TBP0:
            gstate.textureChanged = 1;
        case GE_CMD_TBP1:
        case GE_CMD_TBP2:
        case GE_CMD_TBP3:
        case GE_CMD_TBP4:
        case GE_CMD_TBP5:
        case GE_CMD_TBP6:
        case GE_CMD_TBP7:
            {
                u8 level = (op >> 24) - GE_CMD_TBP0;
                if (level != 0)
                    _log(ERR, GFX, "Texture level > 0 (%d) is not supported [TBP]", level);
                _log(TRC, GFX, "TBP%d: %06x", level, data);
                gstate.texAddr[level] = (gstate.texAddr[level] & 0xff000000) | data;
            }
            break;

        case GE_CMD_TBW0:
            gstate.textureChanged = 1;
        case GE_CMD_TBW1:
        case GE_CMD_TBW2:
        case GE_CMD_TBW3:
        case GE_CMD_TBW4:
        case GE_CMD_TBW5:
        case GE_CMD_TBW6:
        case GE_CMD_TBW7:
            {
                u8 level = (op >> 24) - GE_CMD_TBW0;
                if (level != 0)
                    _log(ERR, GFX, "Texture level > 0 (%d) is not supported [TBW]", level);
                gstate.texBufWidth[level] = data & 0x3ff;
                gstate.texAddr[level] = ((data << 8) & 0xff000000) | (gstate.texAddr[level] & 0xfffff0);
                _log(TRC, GFX, "TBW%d: bufw %03x addr 0x%02x(%06x)", 
                     level, gstate.texBufWidth[level], gstate.texAddr[level] >> 24, gstate.texAddr[level] & 0xfffff0);
            }
            break;

        case GE_CMD_CBP:
            gstate.clutAddr = (gstate.clutAddr & 0xff000000) | (data & 0x00ffffff);
            _log(TRC, GFX, "CBP: 0x[%02x]%06x", gstate.clutAddr >> 24, gstate.clutAddr & 0xffffff);
            break;

        case GE_CMD_CBPH:
            gstate.clutAddr = (gstate.clutAddr & 0x00ffffff) | ((data << 8) & 0x0f000000);
            _log(TRC, GFX, "CBPH: 0x%02x[%06x]", gstate.clutAddr >> 24, gstate.clutAddr & 0xffffff);
            break;

        case GE_CMD_TRXSBP:
            {
                /*u32 x = (data & 0x3ff) + 1;
                u32 y = ((data >> 10) & 0x3ff) + 1;*/
                _log(WRN, GFX, "TRXSBP: %06x", data);
                break;
            }

        case GE_CMD_TRXSBW:
            {
                /*u32 xferSrc = gstate.transfersrc | ((data & 0xff0000) << 8);
                u32 xferSrcW = gstate.transfersrcw & 1023;*/
                _log(WRN, GFX, "TRXSBW: %06x", data);
                break;
            }

        case GE_CMD_TRXDBP:
            {
                /*u32 x = (data & 1023) + 1;
                u32 y = ((data >> 10) & 1023) + 1;*/
                _log(WRN, GFX, "TRXDBP: %06x", data);
                break;
            }

        case GE_CMD_TRXDBW:
            {
                /*u32 xferDst= gstate.transferdst | ((data & 0xff0000) << 8);
                u32 xferDstW = gstate.transferdstw & 1023;*/
                _log(WRN, GFX, "TRXDBW: %06x", data);
                break;
            }
        
        case GE_CMD_TSIZE0:
            gstate.textureChanged = 1;
        case GE_CMD_TSIZE1:
        case GE_CMD_TSIZE2:
        case GE_CMD_TSIZE3:
        case GE_CMD_TSIZE4:
        case GE_CMD_TSIZE5:
        case GE_CMD_TSIZE6:
        case GE_CMD_TSIZE7:
            {
                u8 level = (op >> 24) - GE_CMD_TSIZE0;
                if (level != 0)
                    _log(ERR, GFX, "Texture level > 0 (%d) is not supported [TSIZE]", level);
                gstate.texWidth[level] = 1 << min(data & 0xf, 9);
                gstate.texHeight[level] = 1 << min((data >> 8) & 0xf, 9);

                _log(TRC, GFX, "TSIZE%d: %03x x %03x", level, gstate.texWidth[level], gstate.texHeight[level]);
            }
            break;

        case GE_CMD_TMAP:
            gstate.texMap = data & 3;
            _log(WRN, GFX, "TMAP: %06x [map %d]", data, data & 3);
            break;

        case GE_CMD_TSHADE:
            gstate.texshade = data;
            _log(TRC, GFX, "TSHADE: %06x", gstate.texshade);
            break;

        case GE_CMD_TMODE:
            gstate.numMipmaps      = (data >> 16) & 0x7;
            gstate.mipmapShareClut = (data >>  8) & 0x1;
            gstate.texSwizzle      = (data >>  0) & 0x1;
            _log(TRC, GFX, "TMODE: %d mipmaps, %sshare mipmap with clut, %s use swizzle",
                 gstate.numMipmaps, gstate.mipmapShareClut ? "" : "do not ", gstate.texSwizzle ? "" : "do not ");
            break;

        case GE_CMD_TPSM:
            gstate.texformat = data;
            _log(TRC, GFX, "TPSM: %06x", gstate.texformat);
            break;

        case GE_CMD_CLOAD:
            gstate.clutBlocks = data & 0x3f;
            _log(TRC, GFX, "CLOAD: %d blocks", gstate.clutBlocks);
            break;

        case GE_CMD_CMODE:
            gstate.clutMode =  (data >> 0)  & 3;
            gstate.clutShift = (data >> 2)  & 0x1f;
            gstate.clutMask =  (data >> 8)  & 0xff;
            gstate.clutStart = (data >> 16) & 0x1f;
            _log(TRC, GFX, "CMODE: mode %d, shift 0x%02x, mask 0x%02x, start 0x%02x", gstate.clutMode, gstate.clutShift, gstate.clutMask, gstate.clutStart);
            break;

        case GE_CMD_TFLT:
            gstate.texfilter = data;
            _log(TRC, GFX, "TFLT: %06x", gstate.texfilter);
            break;

        case GE_CMD_TWRAP:
            gstate.texwrap = data;
            _log(TRC, GFX, "TWRAP: %06x", gstate.texwrap);
            break;

        case GE_CMD_TBIAS:
            _log(WRN, GFX, "TBIAS: %06x", data);
            break;

        case GE_CMD_TFUNC:
            {
                u32 func = data & 7;
                u32 alphaUsed = (data >> 8) & 1;
                u32 colorDoubled = (data >> 16) & 1;
                RETexEnvValue texEnvMode = RE_TEXENV_COMBINE;
                _log(TRC, GFX, "TFUNC: effect %d%s%s", func, alphaUsed ? " ALPHA" : "", colorDoubled ? " DOUBLECOLOR" : "");

                if (func >= GE_TFUNC_UNKNOWN1) {
                    _log(ERR, GFX, "Unknown effect %d for TFUNC!", func);
                    break;
                }

                if (colorDoubled || !alphaUsed)
                {
                    switch (func)
                    {
                    case GE_TFUNC_MODULATE:
                        host_setTexEnvParam(RE_TEXENV_COMBINE_RGB, RE_TEXENV_MODULATE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_RGB, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_RGB, RE_TEXENV_SRC_COLOR);
                        host_setTexEnvParam(RE_TEXENV_SRC1_RGB, RE_TEXENV_PREVIOUS);
                        host_setTexEnvParam(RE_TEXENV_OPERAND1_RGB, RE_TEXENV_SRC_COLOR);

                        host_setTexEnvParam(RE_TEXENV_COMBINE_ALPHA, RE_TEXENV_MODULATE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_ALPHA, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_ALPHA, RE_TEXENV_SRC_ALPHA);
                        host_setTexEnvParam(RE_TEXENV_SRC1_ALPHA, RE_TEXENV_PREVIOUS);
                        host_setTexEnvParam(RE_TEXENV_OPERAND1_ALPHA, RE_TEXENV_SRC_ALPHA);
                        break;

                    case GE_TFUNC_DECAL:
                        if (!alphaUsed)
                        {
                            host_setTexEnvParam(RE_TEXENV_COMBINE_RGB, RE_TEXENV_REPLACE);
                            host_setTexEnvParam(RE_TEXENV_SRC0_RGB, RE_TEXENV_TEXTURE);
                            host_setTexEnvParam(RE_TEXENV_OPERAND0_RGB, RE_TEXENV_SRC_COLOR);
                        }
                        else
                        {
                            host_setTexEnvParam(RE_TEXENV_COMBINE_RGB, RE_TEXENV_INTERPOLATE);
                            host_setTexEnvParam(RE_TEXENV_SRC0_RGB, RE_TEXENV_TEXTURE);
                            host_setTexEnvParam(RE_TEXENV_OPERAND0_RGB, RE_TEXENV_SRC_COLOR);
                            host_setTexEnvParam(RE_TEXENV_SRC1_RGB, RE_TEXENV_PREVIOUS);
                            host_setTexEnvParam(RE_TEXENV_OPERAND1_RGB, RE_TEXENV_SRC_COLOR);
                            host_setTexEnvParam(RE_TEXENV_SRC2_RGB, RE_TEXENV_TEXTURE);
                            host_setTexEnvParam(RE_TEXENV_OPERAND2_RGB, RE_TEXENV_SRC_ALPHA);
                        }

                        host_setTexEnvParam(RE_TEXENV_COMBINE_ALPHA, RE_TEXENV_REPLACE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_ALPHA, RE_TEXENV_PREVIOUS);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_ALPHA, RE_TEXENV_SRC_ALPHA);
                        break;

                    case GE_TFUNC_BLEND:
                        host_setTexEnvParam(RE_TEXENV_COMBINE_RGB, RE_TEXENV_INTERPOLATE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_RGB, RE_TEXENV_CONSTANT);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_RGB, RE_TEXENV_SRC_COLOR);
                        host_setTexEnvParam(RE_TEXENV_SRC1_RGB, RE_TEXENV_PREVIOUS);
                        host_setTexEnvParam(RE_TEXENV_OPERAND1_RGB, RE_TEXENV_SRC_COLOR);
                        host_setTexEnvParam(RE_TEXENV_SRC2_RGB, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND2_RGB, RE_TEXENV_SRC_COLOR);

                        host_setTexEnvParam(RE_TEXENV_COMBINE_ALPHA, RE_TEXENV_MODULATE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_ALPHA, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_ALPHA, RE_TEXENV_SRC_ALPHA);
                        host_setTexEnvParam(RE_TEXENV_SRC1_ALPHA, RE_TEXENV_PREVIOUS);
                        host_setTexEnvParam(RE_TEXENV_OPERAND1_ALPHA, RE_TEXENV_SRC_ALPHA);
                        break;

                    case GE_TFUNC_REPLACE:
                        host_setTexEnvParam(RE_TEXENV_COMBINE_RGB, RE_TEXENV_REPLACE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_RGB, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_RGB, RE_TEXENV_SRC_COLOR);

                        host_setTexEnvParam(RE_TEXENV_COMBINE_ALPHA, RE_TEXENV_REPLACE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_ALPHA, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_ALPHA, RE_TEXENV_SRC_ALPHA);
                        break;

                    case GE_TFUNC_ADD:
                        host_setTexEnvParam(RE_TEXENV_COMBINE_RGB, RE_TEXENV_ADD);
                        host_setTexEnvParam(RE_TEXENV_SRC0_RGB, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_RGB, RE_TEXENV_SRC_COLOR);
                        host_setTexEnvParam(RE_TEXENV_SRC1_RGB, RE_TEXENV_PREVIOUS);
                        host_setTexEnvParam(RE_TEXENV_OPERAND1_RGB, RE_TEXENV_SRC_COLOR);

                        host_setTexEnvParam(RE_TEXENV_COMBINE_ALPHA, RE_TEXENV_MODULATE);
                        host_setTexEnvParam(RE_TEXENV_SRC0_ALPHA, RE_TEXENV_TEXTURE);
                        host_setTexEnvParam(RE_TEXENV_OPERAND0_ALPHA, RE_TEXENV_SRC_ALPHA);
                        host_setTexEnvParam(RE_TEXENV_SRC1_ALPHA, RE_TEXENV_PREVIOUS);
                        host_setTexEnvParam(RE_TEXENV_OPERAND1_ALPHA, RE_TEXENV_SRC_ALPHA);
                        break;
                    }
                }
                else
                {
                    switch (func)
                    {
                    case GE_TFUNC_MODULATE:
                        texEnvMode = RE_TEXENV_MODULATE;
                        break;

                    case GE_TFUNC_DECAL:
                        texEnvMode = RE_TEXENV_DECAL;
                        break;

                    case GE_TFUNC_BLEND:
                        texEnvMode = RE_TEXENV_BLEND;
                        break;

                    case GE_TFUNC_REPLACE:
                        texEnvMode = RE_TEXENV_REPLACE;
                        break;

                    case GE_TFUNC_ADD:
                        texEnvMode = RE_TEXENV_ADD;
                        break;
                    }
                }

                host_setTexRgbScale(colorDoubled ? 2.0f : 1.0f);
                host_setTexEnvParam(RE_TEXENV_ENV_MODE, texEnvMode);
            }
            break;

        case GE_CMD_TEC:
            {
                float texEnvColor[4];

                texEnvColor[0] = (float)((data >> 0 ) & 0xff) / 255.0f;
                texEnvColor[1] = (float)((data >> 8 ) & 0xff) / 255.0f;
                texEnvColor[2] = (float)((data >> 16) & 0xff) / 255.0f;
                texEnvColor[3] = 0.0f;

                _log(TRC, GFX, "TEC: %f, %f, %f, %f", texEnvColor[0], texEnvColor[1], texEnvColor[2], texEnvColor[3]);
                host_setTexEnvColor(texEnvColor);
            }
            break;

        case GE_CMD_TFLUSH:
            _log(TRC, GFX, "TFLUSH: %06x", data);
            break;

        case GE_CMD_TSYNC:
            _log(TRC, GFX, "TSYNC: %06x", data);
            break;

        case GE_CMD_FFAR:
            _log(WRN, GFX, "FFAR: %06x", data);
            break;

        case GE_CMD_FDIST:
            _log(WRN, GFX, "FDIST: %06x", data);
            break;

        case GE_CMD_FCOL:
            _log(WRN, GFX, "FCOL: %06x", data);
            break;

        case GE_CMD_TSLOPE:
            _log(WRN, GFX, "TSLOPE: %06x", data);
            break;

        case GE_CMD_PSM:
            _log(WRN, GFX, "PSM: %06x", data);
            break;

        case GE_CMD_CLEAR:
            if (data & 1)
            {
                u8 colMask = (data >> 8) & 1;
                u8 alphaMask = (data >> 9) & 1;
                u8 depthMask = (data >> 10) & 1;
                host_clear(colMask, alphaMask, depthMask);
                host_setMask(colMask, alphaMask, depthMask);
            }
            else
                host_setMask(1, 1, 1);

            gstate.clearmode = data;
            _log(TRC, GFX, "CLEAR: %06x", gstate.clearmode);
            break;

        case GE_CMD_SCISSOR1:
            {
                s32 x1 = data & 0x3ff;
                s32 y1 = data >> 10;
                _log(WRN, GFX, "SCISSOR1: %06x (%d, %d)", data, x1, y1);
            }
            break;

        case GE_CMD_SCISSOR2:
            {
                s32 x2 = data & 0x3ff;
                s32 y2 = data >> 10;
                _log(WRN, GFX, "SCISSOR2: %06x (%d, %d)", data, x2, y2);
            }
            break;

        case GE_CMD_MINZ:
            gstate.minz = data;
            _log(TRC, GFX, "MINZ: %06x", gstate.minz);
            gstate.projChanged = 1;
            break;

        case GE_CMD_MAXZ:
            gstate.maxz = data;
            _log(TRC, GFX, "MAXZ: %06x", gstate.maxz);
            gstate.projChanged = 1;
            break;

        case GE_CMD_CTST:
            _log(WRN, GFX, "CTST: %06x", data);
            break;

        case GE_CMD_CREF:
            _log(WRN, GFX, "CREF: %06x", data);
            break;

        case GE_CMD_CMSK:
            _log(WRN, GFX, "CMSK: %06x", data);
            break;

        case GE_CMD_ATST:
            {
                GEAlphaFunction func = (GEAlphaFunction)(data & 7);
                float value = (float)((data >> 8) & 0xff) / 255.0f;
                _log(TRC, GFX, "ATST: func %d, value %f", func, value);
                host_setAlphaTest(func, value);
            }
            break;

        case GE_CMD_STST:
            _log(WRN, GFX, "STST: %06x", data);
            break;

        case GE_CMD_SOP:
            _log(WRN, GFX, "SOP: %06x", data);
            break;

        case GE_CMD_ZTST:
            {
                GEZTest ztst = (GEZTest)(data & 7);
                _log(TRC, GFX, "ZTST: function %d", ztst);
                if (ztst == GE_ZTEST_GEQUAL)
                    ztst = GE_ZTEST_LESS;
                host_setDepthFunc(ztst);
            }
            break;

        case GE_CMD_ALPHA:
            {
                u8 srcfunc = data & 0xf;
                u8 dstfunc = (data >> 4) & 0xf;
                GEBlendFunction blendop = (GEBlendFunction)((data >> 8) & 0xf);

                _log(TRC, GFX, "ALPHA: operation %d, src func %d, dst func %d", blendop, srcfunc, dstfunc);
                if (srcfunc > 10) {
                    _log(ERR, GFX, "Unknown source function for ALPHA: %d", srcfunc);
                    host_setCapability(RE_CAPABILITY_ALPHA_TEST, 0);
                    break;
                }
                if (dstfunc > 10) {
                    _log(ERR, GFX, "Unknown destination function for ALPHA: %d", dstfunc);
                    host_setCapability(RE_CAPABILITY_ALPHA_TEST, 0);
                    break;
                }
                if (blendop > 5) {
                    _log(ERR, GFX, "Unknown blend function for ALPHA: %d", blendop);
                    host_setCapability(RE_CAPABILITY_ALPHA_TEST, 0);
                    break;
                }
                gstate.srcfunc = srcfunc;
                gstate.dstfunc = dstfunc;

                host_setBlendEquation(blendop);
                setBlendFunc();
            }
            break;

        case GE_CMD_SFIX:
            gstate.sfix[0] = (float)((data >>  0) & 0xff) / 255.0f;
            gstate.sfix[1] = (float)((data >>  8) & 0xff) / 255.0f;
            gstate.sfix[2] = (float)((data >> 16) & 0xff) / 255.0f;
            gstate.sfix[3] = 1.0f;
            _log(TRC, GFX, "SFIX: %f, %f, %f, (%f)", gstate.sfix[0], gstate.sfix[1], gstate.sfix[2], gstate.sfix[3]);
            setBlendFunc();
            break;

        case GE_CMD_DFIX:
            gstate.dfix[0] = (float)((data >>  0) & 0xff) / 255.0f;
            gstate.dfix[1] = (float)((data >>  8) & 0xff) / 255.0f;
            gstate.dfix[2] = (float)((data >> 16) & 0xff) / 255.0f;
            gstate.dfix[3] = 1.0f;
            _log(TRC, GFX, "DFIX: %f, %f, %f, (%f)", gstate.dfix[0], gstate.dfix[1], gstate.dfix[2], gstate.dfix[3]);
            setBlendFunc();
            break;

        case GE_CMD_DTH0:
        case GE_CMD_DTH1:
        case GE_CMD_DTH2:
        case GE_CMD_DTH3:
            _log(WRN, GFX, "DTH%d: %06x", (op >> 24) - GE_CMD_DTH0, data);
            break;

        case GE_CMD_LOP:
            _log(WRN, GFX, "LOP %06x", data);
            break;

        case GE_CMD_ZMSK:
            _log(WRN, GFX, "ZMSK %06x", data);
            break;

        case GE_CMD_PMSKC:
            _log(WRN, GFX, "PMSKC %06x", data);
            break;

        case GE_CMD_PMSKA:
            _log(WRN, GFX, "PMSKA %06x", data);
            break;

        case GE_CMD_TRXKICK:
            _log(WRN, GFX, "TRXKICK: %06x", data);
            break;

        case GE_CMD_TRXSPOS:
            _log(WRN, GFX, "TRXSPOS: %06x", data);
            break;

        case GE_CMD_TRXDPOS:
            _log(WRN, GFX, "TRXDPOS: %06x", data);
            break;

        case GE_CMD_TRXSIZE:
            {
                u32 w = (data & 1023) + 1;
                u32 h = ((data >> 10) & 1023) + 1;
                _log(WRN, GFX, "TRXSIZE: %06x (%d, %d)", data, w, h);
                break;
            }

        default:
            _log(WRN, GFX, "DL Unknown: %08x @ %08x", op, pc);
            break;
        }
        pc += 4;
    }
}
